﻿
namespace Expect.Core.Errors
{
    /// <summary>
    /// Kind of missing element
    /// </summary>
    public enum ElementKind
    {
        /// <summary>Keyword element</summary>
        Keyword,
        /// <summary>Property or field</summary>
        PropertyOrField,
        /// <summary>Method element</summary>
        Method,
        /// <summary>Fixture element</summary>
        Fixture,
        /// <summary>Specification element</summary>
        Specification,
        /// <summary>Fixture element</summary>
        Class,
        /// <summary>Constructor element</summary>
        Constructor
        
    }

    /// <summary>
    /// Error attached if some element is missing
    /// </summary>
    public class ElementNotFoundError : NFitError
    {
        private string errorFormat = "named element \"{0}\" of kind {2} is missing on {3} \"{1}\"";

        /// <summary>
        /// Kind of missing element
        /// </summary>
        public ElementKind Kind { get; private set;}

        /// <summary>
        /// Kind of missing element
        /// </summary>
        public ElementKind TargetKind { get; private set; }

        /// <summary>
        /// Name of the missing element
        /// </summary>
        public string ElementName { get; private set; }

        /// <summary>
        /// Target of the missing element
        /// </summary>
        public string ElementTarget { get; private set; }

        /// <summary>
        /// Additionnal data
        /// </summary>
        public string AdditionnalData { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="kind"></param>
        /// <param name="elementName"></param>
        /// <param name="elementTarget"></param>
        /// <param name="targetkind"></param>
        public ElementNotFoundError(ElementKind kind, string elementName, string elementTarget, ElementKind targetkind)
        {
            Kind = kind;
            ElementName = elementName;
            ElementTarget = elementTarget;
            TargetKind = targetkind;
        }

        /// <summary>
        /// Factory method for a missing keyword
        /// </summary>
        /// <param name="elementName"></param>
        /// <returns></returns>
        public static ElementNotFoundError Keyword(string elementName)
        {
            return new ElementNotFoundError(ElementKind.Keyword,elementName, null, ElementKind.Fixture);
        }

        /// <summary>
        /// Factory method for a missing specification
        /// </summary>
        /// <param name="elementName"></param>
        /// <returns></returns>
        public static ElementNotFoundError Specification(string elementName)
        {
            return new ElementNotFoundError(ElementKind.Specification, elementName, null, ElementKind.Class);
        }

        /// <summary>
        /// Factory method for a missing property or field
        /// </summary>
        /// <param name="elementName"></param>
        /// <param name="elementTarget"></param>
        /// <returns></returns>
        public static ElementNotFoundError PropertyOrField(string elementName, string elementTarget)
        {
            var err = new ElementNotFoundError(ElementKind.PropertyOrField, elementName, elementTarget, ElementKind.Class);
            err.errorFormat = "The class {1} does not contain a property or field \"{0}\"";
            return err;
        }

        /// <summary>
        /// Factory method for a missing class
        /// </summary>
        /// <param name="elementName"></param>
        /// <param name="targetKind"></param>
        /// <returns></returns>
        public static ElementNotFoundError Class(string elementName, ElementKind targetKind)
        {
            var err = new ElementNotFoundError(ElementKind.Class, elementName, null, targetKind);
            err.errorFormat = "No {3} class found for \"{0}\"";
            return err;
        }

        /// <summary>
        /// Factory method for a missing method
        /// </summary>
        /// <param name="elementName"></param>
        /// <param name="elementTarget"></param>
        /// <param name="numArgs"></param>
        /// <returns></returns>
        public static ElementNotFoundError Method(string elementName, string elementTarget, int numArgs)
        {
            var err = new ElementNotFoundError(ElementKind.Method, elementName, elementTarget, ElementKind.Class);
            err.AdditionnalData = numArgs.ToString();
            err.errorFormat = "No method \"{0}\" with {4} arguments found on class \"{1}\"";
            return err;
        }

        /// <summary>
        /// Factory method for a missing method
        /// </summary>
        /// <param name="elementTarget"></param>
        /// <param name="numArgs"></param>
        /// <returns></returns>
        public static ElementNotFoundError Constructor(string elementTarget, int numArgs)
        {
            var err = new ElementNotFoundError(ElementKind.Constructor, null, elementTarget, ElementKind.Class);
            err.AdditionnalData = numArgs.ToString();
            err.errorFormat = "No constructor with {4} arguments found on class \"{1}\"";
            return err;
        }

        /// <summary>
        /// Factory method for a missing fixture
        /// </summary>
        /// <param name="elementName"></param>
        /// <returns></returns>
        public static ElementNotFoundError Fixture(string elementName)
        {
            return new ElementNotFoundError(
                ElementKind.Fixture, 
                elementName, 
                null, 
                ElementKind.Class)
                {
                    errorFormat = "Fixture class \"{0}\" not found"
                };
        }

        /// <summary>
        /// Get a representation of the error
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format(errorFormat, this.ElementName, this.ElementTarget, this.Kind, this.TargetKind, this.AdditionnalData);
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>. 
        ///                 </param><exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.
        ///                 </exception><filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            var comp = obj as ElementNotFoundError;
            if (comp == null) return false;
            return comp.ElementName == ElementName && 
                   comp.ElementTarget == ElementTarget &&
                   comp.Kind == Kind && 
                   comp.TargetKind == TargetKind;
        }

        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}
